﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using $rootnamespace$.DataAccess.Interface;
using $rootnamespace$.Entity;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Reflection;
using System.Linq.Expressions;
using System.Data.Common;

namespace $rootnamespace$.DataAccess.Implementation
{
/// <summary>
    /// Base Data Access implementing Core methods
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class BaseDA<T> : IBaseDA<T> where T : class
    {
        public object DatabaseContext
        {
            get
            {
                return EntityContext;
            }
            set
            {
                EntityContext = (DataModel)value;
            }
        }

        public DataModel _EntityContext;

        public DataModel EntityContext
        {
            get
            {
                if (_EntityContext == null)
                    _EntityContext = new DataModel();

                return _EntityContext;
            }
            set
            {
                _EntityContext = value;
            }
        }

        public IDataAccessAccessPoint DataAccess
        {
            get;
            set;
        }

        public virtual bool Insert(T t)
        {
            EntityContext.AddObject(GetEntitySetName(), t);
            bool result = EntityContext.SaveChanges() > 0;

            return result;
        }

        public T GetById(object id)
        {
            string setName = _EntityContext.DefaultContainerName + "." + GetEntitySetName();
            string keyPropertyName = GetKeyPropertyName(typeof(T));

            EntityKey entityKey = new EntityKey(setName, keyPropertyName, id);

            object result = default(T);
            bool exists = _EntityContext.TryGetObjectByKey(entityKey, out result);

            return result as T;
        }

        private string GetKeyPropertyName(Type type)
        {
            foreach (PropertyInfo pinfo in type.GetProperties())
                foreach (Attribute attr in pinfo.GetCustomAttributes(true))
                    if (attr is EdmScalarPropertyAttribute)
                        if ((attr as EdmScalarPropertyAttribute).EntityKeyProperty)
                            return pinfo.Name;

            throw new ApplicationException("No Entity Key property for " + type.Name);
        }

        private string GetEntitySetName()
        {
            string name = GetObjectSetPropertyInfo().Name;

            return name;
        }

        public virtual int InsertReturnId(T t)
        {
            int result = 0;

            EntityContext.AddObject(GetEntitySetName(), t);

            foreach (PropertyInfo pinfo in t.GetType().GetProperties())
            {
                EdmScalarPropertyAttribute attribute = pinfo.GetCustomAttributes(true).OfType<EdmScalarPropertyAttribute>().FirstOrDefault();
                if ((attribute != null) && (attribute.EntityKeyProperty))
                {
                    result = (int)pinfo.GetValue(t, null);
                    break;
                }
            }

            return result;
        }

        public virtual long InsertReturnLongId(T t)
        {
            long result = 0;

            EntityContext.AddObject(GetEntitySetName(), t);

            foreach (PropertyInfo pinfo in t.GetType().GetProperties())
            {
                EdmScalarPropertyAttribute attribute = pinfo.GetCustomAttributes(true).OfType<EdmScalarPropertyAttribute>().FirstOrDefault();
                if ((attribute != null) && (attribute.EntityKeyProperty))
                {
                    result = (int)pinfo.GetValue(t, null);
                    break;
                }
            }

            return result;
        }

        public virtual bool Update(T t)
        {
            var existingEntity = EntityContext.GetObjectByKey((t as EntityObject).EntityKey);
            if (existingEntity != null)
                EntityContext.ApplyCurrentValues(GetEntitySetName(), t);

            bool result = EntityContext.SaveChanges() > 0;

            return result;
        }

        public virtual bool Delete(T t)
        {
            var existingEntity = EntityContext.GetObjectByKey((t as EntityObject).EntityKey);
            if (existingEntity != null)
                EntityContext.ObjectStateManager.ChangeObjectState(existingEntity, EntityState.Deleted);

            bool result = EntityContext.SaveChanges() > 0;

            return result;
        }

        public virtual IEnumerable<T> GetAll()
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            IEnumerable<T> result = objectSet.ToList<T>();

            return result;
        }

        public virtual IList<T> GetAllAsList()
        {
            IList<T> result = GetAll().ToList<T>();

            return result;
        }

        public virtual HashSet<T> GetAllAsHashSet()
        {
            HashSet<T> result = new HashSet<T>();
            foreach (T t in GetAll())
                result.Add(t);

            return result;
        }

        private PropertyInfo GetObjectSetPropertyInfo()
        {
            Type type = EntityContext.GetType();
            string name = typeof(T).Name;
            PropertyInfo result = type.GetProperty(name);

            if (result == null) // Probably Plurzlized Property Genx (person>people)
                result = type.GetProperties().Where(p => (p.PropertyType.Name == "ObjectSet`1") && (p.PropertyType.GetGenericArguments().Count() == 1) && (p.PropertyType.GetGenericArguments().First() == typeof(T))).FirstOrDefault();

            if (result == null) // Again null, failed to get the property
                throw new ApplicationException(string.Format("Invalid {0} in context with ObjectSet<> return type!", name));

            return result;
        }

        public bool DeleteAll()
        {
            var entityConnection = (System.Data.EntityClient.EntityConnection)EntityContext.Connection;
            DbConnection conn = entityConnection.StoreConnection;

            DbCommand command = conn.CreateCommand();
            command.CommandText = "DELETE FROM " + typeof(T).Name;
            bool result = command.ExecuteNonQuery() == 0;

            return result;
        }

        public IEnumerable<T> Where(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;
            IEnumerable<T> result = objectSet.Where(expression);

            return result;
        }

        public IList<T> WhereAsList(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;
            IList<T> result = objectSet.Where(expression).ToList<T>();

            return result;
        }

        public HashSet<T> WhereAsHashSet(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            HashSet<T> result = new HashSet<T>();
            foreach (T obj in objectSet.Where(expression))
                result.Add(obj);

            return result;
        }

        public int Count()
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            int result = objectSet.Count<T>();

            return result;
        }

        public int Count(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            int result = objectSet.Count<T>(expression);

            return result;
        }

        public bool IsEmpty()
        {
            bool result = this.Count() == 0;

            return result;
        }

        public bool IsEmpty(Expression<Func<T, bool>> expression)
        {
            bool result = this.Count(expression) == 0;

            return result;
        }
    }
}
